﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BaseModel.cs" company="XamlNinja">
//   2011 Richard Griffin and Ollie Riches
// </copyright>
// <summary>
//  Base model providing methods to raise property change event and provides method for generating hash code
//  in derived classes.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace WP7Contrib.Common
{
    using System;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using System.Runtime.Serialization;
    using Helpers;

    /// <summary>
    /// Base model providing methods to raise property change event and provides method for generating hash code
    /// in derived classes.
    /// </summary>
    [DataContract]
    public abstract class BaseModel : INotifyPropertyChanged
    {
        /// <summary>
        /// The property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// The raise property changed.
        /// </summary>
        /// <param name="expression">
        /// The expression.
        /// </param>
        /// <typeparam name="T">The property type.
        /// </typeparam>
        protected void RaisePropertyChanged<T>(Expression<Func<T>> expression)
        {
            if (this.PropertyChanged == null)
            {
                return;
            }

            this.RaisePropertyChanged(ExpressionHelper.ExpressionName(expression));
        }

        /// <summary>
        /// The raise property changed.
        /// </summary>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        protected void RaisePropertyChanged(string propertyName)
        {
            var propertyChanged = this.PropertyChanged;
            if (propertyChanged == null)
            {
                return;
            }

            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// The set property and notify.
        /// </summary>
        /// <param name="currentValue">
        /// The current value.
        /// </param>
        /// <param name="newValue">
        /// The new value.
        /// </param>
        /// <param name="propertyExpression">
        /// The property expression.
        /// </param>
        /// <typeparam name="T">The property type.
        /// </typeparam>
        protected virtual void SetPropertyAndNotify<T>(ref T currentValue, T newValue, Expression<Func<T>> propertyExpression)
        {
            this.SetPropertyAndNotify(ref currentValue, newValue, ExpressionHelper.ExpressionName(propertyExpression));
        }

        /// <summary>
        /// The set property and notify.
        /// </summary>
        /// <param name="currentValue">
        /// The current value.
        /// </param>
        /// <param name="newValue">
        /// The new value.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <typeparam name="T">The property type.
        /// </typeparam>
        protected virtual void SetPropertyAndNotify<T>(ref T currentValue, T newValue, string propertyName)
        {
            if (Equals(currentValue, newValue))
            {
                return;
            }

            currentValue = newValue;
            this.RaisePropertyChanged(propertyName);
        }

        /// <summary>
        /// Combines the values in the object array into a unique hash code.
        /// </summary>
        /// <param name="objects">
        /// The array of objects and there hash codes to be combined into a unique hash code.
        /// </param>
        /// <returns>
        /// Returns the combined hash codes.
        /// </returns>
        protected int CombineHashCodes(params object[] objects)
        {
            var hash = 0;

            foreach (var t in objects)
            {
                hash = (hash << 5) + hash;
                hash ^= this.GetEntryHash(t);
            }

            return hash;
        }

        /// <summary>
        /// Calculates the hash for an object.
        /// </summary>
        /// <param name="entry">
        /// The object to calculate the hash for.
        /// </param>
        /// <returns>
        /// Returns the hash for an object.
        /// </returns>
        private int GetEntryHash(object entry)
        {
            var entryHash = 0x61E04917; // slurped from .Net runtime internals...

            if (entry != null)
            {
                var subObjects = entry as object[];
                entryHash = subObjects != null ? this.CombineHashCodes(subObjects) : entry.GetHashCode();
            }

            return entryHash;
        }
    }
}
